//      Copyright (c) Microsoft Corporation. All rights reserved.
#include "DPWSSideShow.h"
#include <string>
#include <wsdapi.h>
#include <functiondiscovery.h>
#include <functiondiscoverykeys.h>
#include "SideShowServiceTypeProxy.h"

CDPWSSideShow::CDPWSSideShow() :
    m_bInitialized(FALSE),
    m_clientID(0)
{
    // use computer name as the identification of the PC
    DWORD size = MAX_COMPUTERNAME_LENGTH + 1;

    BOOL rv = GetComputerName(m_ComputerName, &size);

    m_clientID = m_ComputerName;

    if (!rv)
    {
        m_clientID = L"No computer name";
    }
}

CDPWSSideShow::~CDPWSSideShow()
{
}

// entry of DPWS SideShow codes
CSideShowHW* CreateSideShowHW()
{
    return new(std::nothrow) CDPWSSideShow();
}

// Initialize. Query the function instance and create proxy for DPWS SideShow Service
HRESULT CDPWSSideShow::Initialize(PCWSTR instance, CSideShowButtonEventSink* eventSink)
{
    m_bInitialized = FALSE;
    wstring clientID(m_clientID);

    HRESULT hr = m_DeviceProxyProvider.Initialize(clientID, eventSink);

    if (S_OK != hr)
        return hr;

    wstring instancePath(instance);

    hr = m_DeviceDiscovery.Initialize(instancePath, &m_DeviceProxyProvider);
    
    if (S_OK != hr)
        return hr;

    m_bInitialized = TRUE;

    return hr;
}

// Wrapping the outbound attachment
class COutboundAttachmentInfo
{
public:
    COutboundAttachmentInfo(const BYTE* pBuffer, DWORD size);
    HRESULT Initialize();
    ~COutboundAttachmentInfo();
    IWSDOutboundAttachment* GetWSDOutboundAttachment();
    HRESULT SendOut();
private:
    const BYTE* m_pBuffer;
    DWORD m_Size;
    IWSDOutboundAttachment* m_pOutboundAttachment;
};

COutboundAttachmentInfo::COutboundAttachmentInfo(const BYTE* pBuffer, DWORD size) :
    m_pBuffer(pBuffer),
    m_Size(size)
{
}

HRESULT COutboundAttachmentInfo::Initialize()
{
    HRESULT hr;
    hr = WSDCreateOutboundAttachment(&m_pOutboundAttachment);
#if DBG
    if (S_OK != hr)
        OutputDebugString(L"WSDCreateOutboundAttachment failed!");
#endif
    return hr;
}

COutboundAttachmentInfo::~COutboundAttachmentInfo()
{
    m_pOutboundAttachment->Release();
}

IWSDOutboundAttachment* COutboundAttachmentInfo::GetWSDOutboundAttachment()
{
    return m_pOutboundAttachment;
}

HRESULT COutboundAttachmentInfo::SendOut()
{
    DWORD total = m_Size;
    DWORD left = total;
    HRESULT hr = S_OK;

    // cannot call Close() before Write()
    if( 0 == left )
    {
        return S_OK;
    }

    while( S_OK == hr && 0 < left )
    {
        DWORD written = 0;

        // Write multiple times until this block has been consumed
        hr = m_pOutboundAttachment->Write(
            m_pBuffer + (total - left),
            left,
            &written);

        left -= written;
    }

    // cleanup
    if( S_OK == hr )
    {
        hr = m_pOutboundAttachment->Close();
    }
    else
    {
        m_pOutboundAttachment->Abort();
    }

    return hr;
}

// The outbound attachment should be sent in another thread, and the WSD API will take care of the syncronization
DWORD WINAPI SendAttachmentThreadProc(LPVOID lpParameter)
{
    HRESULT hr;
    COutboundAttachmentInfo* pAttach = (COutboundAttachmentInfo*) lpParameter;

    hr = pAttach->SendOut();
    delete pAttach;
    return hr;
}

const TCHAR DEFAULT_ANY_ELEMENT_NAMESPACE[] = L"http://schemas.xmlsoap.org/ws/2006/02/devprof";
const TCHAR DEFAULT_ANY_ELEMENT_NAME[] = L"Hosted";

static WSDXML_ELEMENT* CreateAnyElement()
{
    WSDXML_NAME* pName = NULL;
    HRESULT hr = WSDXMLGetNameFromBuiltinNamespace(
        DEFAULT_ANY_ELEMENT_NAMESPACE,
        DEFAULT_ANY_ELEMENT_NAME,
        &pName
        );

    if (FAILED(hr))
    {
        return 0;
    }

    WSDXML_ELEMENT* pElement = NULL;
    hr = WSDXMLBuildAnyForSingleElement(pName, L"Test", &pElement);
    if (FAILED(hr))
    {
        return 0;
    }

    return pElement;
}

// send bitmap to device
HRESULT CDPWSSideShow::DisplayBitmap(const BYTE* pbBitmapData, DWORD cbBitmapData, DWORD dwRenderFlags)
{
    HRESULT hr = S_OK;

    if (cbBitmapData == 0)
        return hr;  

#if DBG
    WCHAR wszBuff[256];
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), 
        L"DisplayBitmap  flag %d, data size %d\r\n", 
        dwRenderFlags, cbBitmapData);
    OutputDebugString(wszBuff);
#endif

    if (FALSE == m_bInitialized)
        return E_FAIL;

    CComPtr<ISideShowServiceTypeProxy> pProxy;
    m_DeviceProxyProvider.GetProxy(pProxy);

    if (!pProxy)
        return E_FAIL;

    // Start another thread to write our picture data to IWSDOutboundAttachment.
    // Microsoft WSDAPI make is that IWSDOutboundAttachment::Write 
    // won't return until the message has been started and your data has been 
    // successfully sent to the HTTP layer for transmission.
    // For more information, see:
    // http://blogs.msdn.com/dandris/archive/2008/01/17/help-i-can-t-read-or-write-attachments.aspx
    COutboundAttachmentInfo* pAttachmentInfo = new(std::nothrow) COutboundAttachmentInfo(pbBitmapData, cbBitmapData);

    if (0 == pAttachmentInfo)
    {
        return E_OUTOFMEMORY;
    }

    hr = pAttachmentInfo->Initialize();
    if (S_OK != hr)
    {
        delete pAttachmentInfo;
        return hr;
    }

    HANDLE hThread;
    DWORD threadID = 0;

    hThread = CreateThread(NULL,
        0,
        SendAttachmentThreadProc,
        (LPVOID)pAttachmentInfo,
        0,
        &threadID
        );

    if (hThread == NULL)
    {
#if DBG
        OutputDebugString(L"Create thread for SendAttachmentThreadProc failed!");
#endif
        delete pAttachmentInfo;
        return E_FAIL;
    }

    CloseHandle(hThread);
    
    SIDE_SHOW_FRAME_STREAM_TYPE frame = {0};
    frame.ClientID = m_clientID;
    frame.StreamIncludesSingleFrame = TRUE;
    frame.Any = CreateAnyElement();
    frame.FrameStream = pAttachmentInfo->GetWSDOutboundAttachment();

    hr = pProxy->SendSideShowFrameStream(&frame);

    // clean up. pAttachmentInfo is deleted in SendAttachmentThreadProc
    WSDXMLCleanupElement(frame.Any);

    return hr;
}

// get caps from the device
HRESULT CDPWSSideShow::GetCaps(ULONG* pWidth, ULONG* pHeight, ULONG* pDepth)
{
    if (FALSE == m_bInitialized)
    {
#if DBG
        OutputDebugString(L"GetCaps failed, CDPWSSideShow not initialized");
#endif
        return E_FAIL;
    }

    CComPtr<ISideShowServiceTypeProxy> pProxy;
    m_DeviceProxyProvider.GetProxy(pProxy);

    if (!pProxy)
        return E_FAIL;

    SIDE_SHOW_ELEMENTS_TYPE* out = NULL;
    HRESULT hr;
    hr = pProxy->GetSideShowElements(&out);

    if (S_OK != hr)
        return hr;

    *pWidth = out->SideShowAreaWidth;
    *pHeight = out->SideShowAreaHeight;
    *pDepth = out->ColorDepth;
    WSDFreeLinkedMemory(out);

#if DBG
    WCHAR wszBuff[256];
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), 
        L"GetCaps %d x %d x %d\r\n", 
        *pWidth, *pHeight, *pDepth);
    OutputDebugString(wszBuff);
#endif
    return S_OK;
}

const WCHAR* CDPWSSideShow::GetClientID()
{
    return m_clientID;
}

CDPWSSideShowEventSink::CDPWSSideShowEventSink() :
    m_pButtonEventSink(0)
{
}

CDPWSSideShowEventSink::~CDPWSSideShowEventSink()
{
}

void CDPWSSideShowEventSink::Initialize(const wstring& clientID, CSideShowButtonEventSink* pSink)
{
    m_clientID = clientID;
    m_pButtonEventSink = pSink;
}

// button events call back
HRESULT CDPWSSideShowEventSink::SideShowButtonPressedEvent( 
            /* [in] */ SIDE_SHOW_BUTTON_PRESS_TYPE *pButtonValue)
{
#if DBG
    WCHAR wszBuff[256];
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), 
        L"SideShowButtonPressedEvent client %s button %s\r\n", 
        pButtonValue->ClientID, pButtonValue->Button);
    OutputDebugString(wszBuff);
#endif

    if (pButtonValue->ClientID != m_clientID)
        return S_OK;

    if (wcscmp(pButtonValue->Button, L"Previous") == 0)
    {
        m_pButtonEventSink->OnButton(CDevice::ButtonPrevious);
        return S_OK;
    }

    if (wcscmp(pButtonValue->Button, L"Next") == 0)
    {
        m_pButtonEventSink->OnButton(CDevice::ButtonNext);
        return S_OK;
    }

    if (wcscmp(pButtonValue->Button, L"Up") == 0)
    {
        m_pButtonEventSink->OnButton(CDevice::ButtonUp);
        return S_OK;
    }

    if (wcscmp(pButtonValue->Button, L"Down") == 0)
    {
        m_pButtonEventSink->OnButton(CDevice::ButtonDown);
        return S_OK;
    }

    if (wcscmp(pButtonValue->Button, L"Select") == 0)
    {
        m_pButtonEventSink->OnButton(CDevice::ButtonSelect);
        return S_OK;
    }

    if (wcscmp(pButtonValue->Button, L"Menu") == 0)
    {
        m_pButtonEventSink->OnButton(CDevice::ButtonMenu);
        return S_OK;
    }

    if (wcscmp(pButtonValue->Button, L"Back") == 0)
    {
        m_pButtonEventSink->OnButton(CDevice::ButtonBack);
        return S_OK;
    }

    return S_OK;
}
